package com.joe.xl.devcommon1.controller;

import org.omg.CORBA.TIMEOUT;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RMap;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * 秒杀场景下，利用redis 来做分布式锁
 *
 * @Author joexl <joe_xuli@163.com>
 * @Since 1.0
 * @Date 2020/10/23
 */
@RestController
@RequestMapping("/demoredis")
public class ConcurrentRedisController {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @RequestMapping("/test")
    public String test1(){
        return "hello world";
    }

    /***
     * 1.加过期时间，最后释放锁
     * 2.过期时间之内，未释放锁，结果另一个线程获取了锁，然后第一个线程释放了锁。并发量大的时候就一直获取不到锁
     * 3.防止其他线程删除不属于自己的锁，设置属于自己的锁对应的value值
     * 4.设置过期时间合理性的问题，获取锁后开一个线程，timer去定期强行续命，定时器的时间大概设置超时时间的三分之一
     * 基于上面4的问题，已经有成熟的解决方案 redisson
     * @param productSysno
     * @return
     */
    @RequestMapping("/reduceProductWithRedis")
    public String reduceProductWithRedis(Long productSysno){
        String lockkey = "pro" + productSysno;
        String clientID = UUID.randomUUID().toString();

        try {
            // Boolean res = stringRedisTemplate.opsForValue().setIfAbsent(lockkey, "reducePro");
            // stringRedisTemplate.expire(lockkey, 10, TimeUnit.SECONDS);
            Boolean res = stringRedisTemplate.opsForValue().setIfAbsent(lockkey,
                    clientID, 10, TimeUnit.SECONDS);

            if (!res) {
                return "system busy...";
            }

            String stock = stringRedisTemplate.opsForValue().get("stock");
            System.out.println("before.." + stock);

            if (Objects.nonNull(stock)) {
                stringRedisTemplate.opsForValue().set("stock",
                        String.valueOf(Integer.parseInt(stock) - 1));
            }

            // 单机情况下没问题
            /*synchronized (this) {
                String stock = stringRedisTemplate.opsForValue().get("stock");
                System.out.println("before.." + stock);

                if (Objects.nonNull(stock)) {
                    stringRedisTemplate.opsForValue().set("stock",
                            String.valueOf(Integer.parseInt(stock) - 1));
                }
            }*/
        }finally {
            // 释放锁
            String redisClientid = stringRedisTemplate.opsForValue().get(lockkey);
            if(clientID.equals(redisClientid))
                stringRedisTemplate.delete(lockkey);
        }

        return "end";
    }

    /***
     * redisson分布式实现原理
     * 如果未能加锁，则while循环，自旋一直尝试加锁
     * 如果加锁成功，则另起一个后台线程每隔十秒检查是否还持有锁，如果持有则延长锁的时间
     * @param productSysno
     * @return
     */
    @RequestMapping("/reduceProductWithRedisson")
    public String reduceProductWithRedisson(Long productSysno){
        String lockkey = "pro" + productSysno;
        RLock rlock = redissonClient.getLock(lockkey);

        try {
//            rlocak.lock(30, TimeUnit.SECONDS);
            boolean res = rlock.tryLock(10,10, TimeUnit.SECONDS);

            if(res) {
                /*String stock = stringRedisTemplate.opsForValue().get("stock");
                System.out.println("before.." + stock);

                String modi = String.valueOf(Integer.parseInt(stock) - 1);
                if (Objects.nonNull(stock)) {
                    stringRedisTemplate.opsForValue().set("stock", modi);
                }*/

                RMap<String, Object> map = redissonClient.getMap("stock");
                String modi = String.valueOf(Integer.parseInt(map.get("stock").toString()) - 1);
                map.put("stock", modi);
            }
        } catch (Exception e){
            rlock.unlock();
        } finally {
            rlock.unlock();
        }

        return "end";
    }
}
